# 在 app.py 顶部添加日志配置（已导入的模块无需重复导入）
import logging
from flask import Flask, request, render_template, redirect, url_for, flash
# 其他必要导入（如模型相关、数据库等）

# 配置日志，显示调试信息
logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'  # 包含时间和模块名，更清晰
)

# 假设你的 Flask 应用初始化代码
app = Flask(__name__)
# 其他配置（如 SECRET_KEY、数据库连接等）
from flask import Flask, render_template, request, redirect, url_for, session, flash
from werkzeug.security import generate_password_hash, check_password_hash
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.preprocessing import LabelEncoder, StandardScaler
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, confusion_matrix
import joblib
import os
import sklearn
from datetime import datetime, timedelta
import shutil
import warnings
import csv
from sqlalchemy import func
import plotly
import plotly.graph_objs as go
import json

warnings.filterwarnings('ignore')

# 配置日志
import logging

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[logging.FileHandler('app.log'), logging.StreamHandler()]
)
logger = logging.getLogger(__name__)

app = Flask(__name__)
app.secret_key = os.urandom(24).hex()  # 使用更安全的密钥生成方式
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

# 导入SQLAlchemy并初始化
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy(app)


# 用户模型
class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(8), unique=True, nullable=False)
    password_hash = db.Column(db.String(128), nullable=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    is_admin = db.Column(db.Boolean, default=False)
    last_login = db.Column(db.DateTime)  # 添加这一行！
    prediction_count = db.Column(db.Integer, default=0)

    def set_password(self, password):
        self.password_hash = generate_password_hash(password)

    def check_password(self, password):
        return check_password_hash(self.password_hash, password)


# 新增：数据收集模型
class PredictionRecord(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    age = db.Column(db.Integer, nullable=False)
    gender = db.Column(db.String(10), nullable=False)
    travel_purpose = db.Column(db.String(20), nullable=False)
    booking_window = db.Column(db.Integer, nullable=False)
    flight_duration = db.Column(db.Integer, nullable=False)
    cabin_class = db.Column(db.String(20), nullable=False)
    is_long_flight = db.Column(db.Boolean, nullable=False)
    is_early_booking = db.Column(db.Boolean, nullable=False)
    prediction = db.Column(db.Integer, nullable=False)  # 0或1
    probability = db.Column(db.Float, nullable=False)  # 付费概率
    created_at = db.Column(db.DateTime, default=datetime.utcnow)

    # 关系定义
    user = db.relationship('User', backref=db.backref('predictions', lazy=True))


# 新增：模型训练记录
class ModelTrainingLog(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    model_name = db.Column(db.String(50), nullable=False)
    version = db.Column(db.String(50), nullable=False)
    accuracy = db.Column(db.Float)
    precision = db.Column(db.Float)
    recall = db.Column(db.Float)
    f1 = db.Column(db.Float)
    training_size = db.Column(db.Integer)
    training_time = db.Column(db.Float)  # 训练耗时(秒)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)


# 配置路径
DATA_PATH = 'data/flight_passengers.csv'
NEW_DATA_PATH = 'data/new_passengers.csv'
ARCHIVE_PATH = 'data/archive/'
FEATURE_COLUMNS_PATH = 'models/feature_columns.pkl'
MODEL_PATH = 'models/seat_prediction_model.pkl'
SCALER_PATH = 'models/scaler.pkl'
ENCODERS_PATH = 'models/encoders.pkl'
MODEL_VERSION_PATH = 'models/model_version.txt'
MODEL_HISTORY_PATH = 'models/history/'
PERFORMANCE_HISTORY_PATH = 'models/performance_history.csv'

# 确保目录存在
for dir_path in ['models', 'data', 'templates', ARCHIVE_PATH, MODEL_HISTORY_PATH]:
    if not os.path.exists(dir_path):
        os.makedirs(dir_path)


# 登录装饰器
def login_required(f):
    import functools
    @functools.wraps(f)
    def decorated_function(*args, **kwargs):
        if 'user_id' not in session:
            flash('请先登录才能访问该页面', 'warning')
            return redirect(url_for('login', next=request.url))
        return f(*args, **kwargs)

    return decorated_function


# 管理员权限装饰器
def admin_required(f):
    import functools
    @functools.wraps(f)
    def decorated_function(*args, **kwargs):
        if 'user_id' not in session:
            flash('请先登录才能访问该页面', 'warning')
            return redirect(url_for('login', next=request.url))

        user = User.query.get(session['user_id'])
        if not user.is_admin:
            flash('您没有访问此页面的权限', 'danger')
            return redirect(url_for('index'))

        return f(*args, **kwargs)

    return decorated_function


# 确保模板文件存在
def create_template_files():
    """创建所有必要的HTML模板文件，包括登录注册页面和管理页面"""
    templates = {
        'login.html': '''<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>用户登录 - 航班乘客付费选座预测系统</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
</head>
<body class="bg-gray-100">
    <div class="container mx-auto px-4 py-12 max-w-md">
        <header class="text-center mb-8">
            <h1 class="text-3xl font-bold text-gray-800">航班乘客付费选座预测系统</h1>
            <p class="text-gray-600 mt-2">用户登录</p>
        </header>

        <div class="bg-white rounded-lg shadow-md p-8">
            {% with messages = get_flashed_messages(with_categories=true) %}
                {% if messages %}
                    {% for category, message in messages %}
                        <div class="mb-4 p-3 rounded-md {% if category == 'success' %}bg-green-100 text-green-700{% elif category == 'warning' %}bg-yellow-100 text-yellow-700{% else %}bg-red-100 text-red-700{% endif %}" id="auto-hide-alert">
                            {{ message }}
                        </div>
                    {% endfor %}

                    <script>
                        // 10秒后自动隐藏提示
                        const alertElements = document.querySelectorAll('#auto-hide-alert');
                        alertElements.forEach(element => {
                            setTimeout(() => {
                                element.style.opacity = '0';
                                element.style.transition = 'opacity 0.5s ease';
                                setTimeout(() => {
                                    element.remove();
                                }, 500);
                            }, 10000); // 10秒
                        });
                    </script>
                {% endif %}
            {% endwith %}

            <form action="/login" method="post" class="space-y-4">
                <div>
                    <label class="block text-gray-700 mb-1" for="username">用户名</label>
                    <div class="relative">
                        <span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-500">
                            <i class="fa fa-user"></i>
                        </span>
                        <input type="text" id="username" name="username" minlength="1" maxlength="8" required
                            class="w-full pl-10 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
                    </div>
                </div>

                <div>
                    <label class="block text-gray-700 mb-1" for="password">密码</label>
                    <div class="relative">
                        <span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-500">
                            <i class="fa fa-lock"></i>
                        </span>
                        <input type="password" id="password" name="password" minlength="6" required
                            class="w-full pl-10 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
                    </div>
                </div>

                <div class="text-center mt-6">
                    <button type="submit" 
                        class="bg-blue-600 text-white py-3 px-8 rounded-md hover:bg-blue-700 transition w-full">
                        <i class="fa fa-sign-in mr-2"></i>登录
                    </button>
                </div>
            </form>

            <div class="mt-6 text-center">
                <p class="text-gray-600">还没有账号？ <a href="/register" class="text-blue-600 hover:underline">立即注册</a></p>
            </div>
        </div>
    </div>
</body>
</html>''',

        'register.html': '''<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>用户注册 - 航班乘客付费选座预测系统</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
</head>
<body class="bg-gray-100">
    <div class="container mx-auto px-4 py-12 max-w-md">
        <header class="text-center mb-8">
            <h1 class="text-3xl font-bold text-gray-800">航班乘客付费选座预测系统</h1>
            <p class="text-gray-600 mt-2">用户注册</p>
        </header>

        <div class="bg-white rounded-lg shadow-md p-8">
            {% with messages = get_flashed_messages(with_categories=true) %}
                {% if messages %}
                    {% for category, message in messages %}
                        <div class="mb-4 p-3 rounded-md {% if category == 'success' %}bg-green-100 text-green-700{% elif category == 'warning' %}bg-yellow-100 text-yellow-700{% else %}bg-red-100 text-red-700{% endif %}" id="auto-hide-alert">
                            {{ message }}
                        </div>
                    {% endfor %}

                    <script>
                        // 10秒后自动隐藏提示
                        const alertElements = document.querySelectorAll('#auto-hide-alert');
                        alertElements.forEach(element => {
                            setTimeout(() => {
                                element.style.opacity = '0';
                                element.style.transition = 'opacity 0.5s ease';
                                setTimeout(() => {
                                    element.remove();
                                }, 500);
                            }, 10000); // 10秒
                        });
                    </script>
                {% endif %}
            {% endwith %}

            <form action="/register" method="post" class="space-y-4">
                <div>
                    <label class="block text-gray-700 mb-1" for="username">用户名</label>
                    <div class="relative">
                        <span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-500">
                            <i class="fa fa-user"></i>
                        </span>
                        <input type="text" id="username" name="username" minlength="1" maxlength="8" required
                            class="w-full pl-10 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-极2 focus:ring-blue-500">
                        <p class="text-xs text-gray-500 mt-1">用户名长度为1-8个字符</p>
                    </div>
                </div>

                <div>
                    <label class="block text-gray-700 mb-1极" for="password">密码</label>
                    <div class="relative">
                        <span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-500">
                            <i class="fa fa-lock"></i>
                        </span>
                        <input type="password" id="password" name="password" minlength="6" required
                            class="w-full pl-10 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
                        <p class="text-xs text-gray-500 mt-1">密码至少6个字符</p>
                    </div>
                </div>

                <div>
                    <label class="block text-gray-700 mb-1" for="confirm_password">确认密码</label>
                    <div class="relative">
                        <span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-500">
                            <i class="fa fa-lock"></i>
                        </span>
                        <input type="password" id="confirm_password" name="confirm_password" required
                            class="w-full pl-10 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
                    </div>
                </div>

                <div class="text-center mt-6">
                    <button type="submit" 
                        class="bg-blue-600 text-white py-3 px-8 rounded-md hover:bg-blue-700 transition w-full">
                        <i class="fa fa-user-plus mr-2"></i>注册
                    </button>
                </div>
            </form>

            <div class="mt-6 text-center">
                <p class="text-gray-600">已有账号？ <a href="/login" class="text-blue-600 hover:underline">立即登录</a></p>
            </div>
        </div>
    </div>
</body>
</html>''',

        'index.html': '''<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>航班乘客付费选座预测系统 - 首页</title>
    <script src="极https://cdn.tailwindcss.com"></script>
    <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
</head>
<body class="bg-gray-100">
    <div class="container mx-auto px-4 py-8 max-w-5xl">
        <header class="text-center mb-8">
            <h1 class="text-3xl font-bold text-gray-800">航班乘客付费选座预测系统</h1>
            <p class="text-gray-600 mt-2">基于机器学习模型的乘客行为分析与预测</p>
        </header>

        <!-- 导航菜单 -->
        <nav class="bg-white rounded-lg shadow-md p-4 mb-8">
            <ul class="flex flex-wrap gap-4 justify-center">
                <li><a href="/" class="text-blue-600 hover:text-blue-800 flex items-center"><i class="fa fa-home mr-1"></i>首页</a></li>
                <li><a href="/result" class="text-blue-600 hover:text-blue-800 flex items-center"><i class="fa fa-bar-chart mr-1"></i>预测结果</a></li>
                <li><a href="/suggestions" class="text-blue-600 hover:text-blue-800 flex items-center"><i class="fa fa-lightbulb-o mr-1"></i>营销建议</a></li>
                {% if current_user.is_admin %}
                <li><a href="/admin" class="text-purple-600 hover:text-purple-800 flex items-center"><i class="fa fa-cog mr-1"></i>系统管理</a></li>
                {% endif %}
                <li><a href="/logout" class="text-red-600 hover:text-red-800 flex items-center"><i class="fa fa-sign-out mr-1"></i>退出登录</a></li>
                <li class="ml-auto text-gray-600">当前用户: {{ current_user.username }}</li>
            </ul>
        </nav>

        <!-- 乘客信息填写表单 -->
        <div class="bg-white rounded-lg shadow-md p-6 mb-8">
            <h2 class="text-xl font-semibold mb-6 text-gray-700 text-center">
                <i class="fa fa-user-circle mr-2"></i>乘客信息填写
            </h2>

            <form id="predictionForm" action="/predict" method="post" class="space-y-4 max-w-3xl mx-auto">
                <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
                    <div>
                        <label class="block text-gray-700 mb-1" for="age">年龄</label>
                        <input type="number" id="age" name="age" min="18" max="80" required
                            value="{{ form_data.age if form_data else '' }}"
                            class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
                    </div>

                    <div>
                        <label class="block text-gray-700 mb-1" for="gender">性别</label>
                        <select id="gender" name="gender" required
                            class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
                            <option value="Male" {% if form_data and form_data.gender == 'Male' %}selected{% endif %}>男性</option>
                            <option value="Female" {% if form_data and form_data.gender == 'Female' %}selected{% endif %}>女性</option>
                            <option value="Other" {% if form_data and form_data.gender == 'Other' %}selected{% endif %}>其他</option>
                        </select>
                    </div>

                    <div>
                        <label class="block text-gray-700 mb-1" for="travel_purpose">旅行目的</label>
                        <select id="travel_purpose" name="travel_purpose" required
                            class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
                            <option value="Business" {% if form_data and form_data.travel_purpose == 'Business' %}selected{% endif %}>商务</option>
                            <option value="Leisure" {% if form_data and form_data.travel_purpose == 'Leisure' %}selected{% endif %}>休闲</option>
                            <option value="Family" {% if form_data and form_data.travel_purpose == 'Family' %}selected{% endif %}>家庭</option>
                        </select>
                    </div>

                    <div>
                        <label class="block text-gray-700 mb-1" for="booking_window">预订提前天数</label>
                        <input type="number" id="booking_window" name="booking_window" min="1" max="180" required
                            value="{{ form_data.booking_window if form_data else '' }}"
                            class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
                    </div>

                    <div>
                        <label class="block text-gray-700 mb-1" for="flight_duration">飞行时长（小时）</label>
                        <input type="number" id="flight_duration" name="flight_duration" min="1" max="24" required
                            value="{{ form_data.flight_duration if form_data else '' }}"
                            class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
                    </div>

                    <div>
                        <label class="block text-gray-700 mb-1" for="cabin_class">客舱等级</label>
                        <select id="cabin_class" name="cabin_class" required
                            class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
                            <option value="Economy" {% if form_data and form_data.cabin_class == 'Economy' %}selected{% endif %}>经济舱</option>
                            <option value="Premium Economy" {% if form_data and form_data.cabin_class == 'Premium Economy' %}selected{% endif %}>超级经济舱</option>
                            <option value="Business" {% if form_data and form_data.cabin_class == 'Business' %}selected{% endif %}>商务舱</option>
                            <option value="First" {% if form_data and form_data.cabin_class == 'First' %}selected{% endif %}>头等舱</option>
                        </select>
                    </div>
                </div>

                <div class="text-center mt-8">
                    <button type="submit" 
                        class="bg-blue-600 text-white py-3 px-8 rounded-md hover:bg-blue-700 transition">
                        <i class="fa fa-calculator mr-2"></i>预测付费意愿
                    </button>
                </div>
            </form>
        </div>

        <footer class="mt-12 text-center text-gray-500 text-sm">
            <p>航班乘客付费选座预测系统 &copy; 2023</p>
        </footer>
    </div>
</body>
</html>''',

        'result.html': '''<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>预测结果 - 航班乘客付费选座预测系统</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
</head>
<body class="bg-gray-100">
    <div class="container mx-auto px-4 py-8 max-w-4xl">
        <header class="text-center mb-8">
            <h1 class="text-3xl font-bold text-gray-800">航班乘客付费选座预测系统</h1>
            <p class="text-gray-600 mt-2">预测结果分析</p>
        </header>

        <!-- 导航菜单 -->
        <nav class="bg-white rounded-lg shadow-md p-4 mb-8">
            <ul class="flex flex-wrap gap-4 justify-center">
                <li><a href="/" class="text-blue-600 hover:text-blue-800 flex items-center"><i class="fa fa-home mr-1"></i>首页</a></li>
                <li><a href="/result" class="text-blue-600 hover:text-blue-800 flex items-center"><i class="fa fa-bar-chart mr-1"></i>预测结果</a></li>
                <li><a href="/suggestions" class="text-blue-600 hover:text-blue-800 flex items-center"><i class="fa fa-lightbulb-o mr-1"></极i>营销建议</a></li>
                {% if current_user.is_admin %}
                <li><a href="/admin" class="text-purple-600 hover:text-purple-800 flex items-center"><i class="fa fa-cog mr-1"></i>系统管理</a></li>
                {% endif %}
                <li><a href="/logout" class="text-red-600 hover:text-red-800 flex items-center"><i class="fa fa-sign-out mr-1"></i>退出登录</a></li>
            </ul>
        </nav>

        {% if not result %}
        <div class="bg-yellow-50 border border-yellow-200 text-yellow-700 p-4 rounded-md mb-6 text-center">
            <p>请先在首页填写乘客信息并进行预测</p>
            <a href="/" class="text-blue-600 hover:underline mt-2 inline-block">返回首页</a>
        </div>
        {% else %}
        <!-- 预测结果 -->
        <div class="bg-white rounded-lg shadow-md p-6 mb-8">
            <h2 class="text-xl font-semibold mb-6 text-gray-700 text-center">
                <i class="fa fa-bar-chart mr-2"></i>付费选座预测结果
            </h2>

            <div class="text-center mb-8">
                <p class="text-2xl font-medium mb-4">
                    {{ '该乘客很可能会付费选座' if result.prediction else '该乘客不太可能付费选座' }}
                </p>
                <p class="text-gray-600 mb-6">付费概率: <span class="text-2xl font-bold text-blue-600">{{ (result.probability * 100)|round(1) }}%</span></p>

                <div class="w-full max-w-xl mx-auto bg-gray-200 rounded-full h-6 mb-2">
                    <div class="h-6 rounded-full" 
                         style="width: {{ (result.probability * 100)|round(0) }}%; 
                                background-color: {{ '#10b981' if result.probability >= 0.7 else '#f59e0b' if result.probability >= 0.3 else '#ef4444' }}">
                    </div>
                </div>
            </div>
        </div>

        <!-- 操作按钮 -->
        <div class="flex justify-between mt-8">
            <a href="/" class="bg-gray-600 text-white py-2 px-6 rounded-md hover:bg-gray-700 transition flex items-center">
                <i class="fa fa-arrow-left mr-2"></i>返回重新填写
            </a>
            <a href="/suggestions" class="bg-purple-600 text-white py-2 px-6 rounded-md hover:bg-purple-700 transition flex items-center">
                查看营销建议 <i class="fa fa-arrow-right mr-2"></i>
            </a>
        </div>
        {% endif %}

        <footer class="mt-12 text-center text-gray-500 text-sm">
            <p>航班乘客付费选座预测系统 &copy; 2023</p>
        </footer>
    </div>
</body>
</html>''',

        'suggestions.html': '''<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>营销建议 - 航班乘客付费选座预测系统</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
</head>
<body class="bg-gray-100">
    <div class="container mx-auto px-4 py-8 max-w-4xl">
        <header class="text-center mb-8">
            <h1 class="text-3xl font-bold text-gray-800">航班乘客付费选座预测系统</h1>
            <p class="text-gray-600 mt-2">个性化营销建议</p>
        </header>

        <!-- 导航菜单 -->
        <nav class="bg-white rounded-lg shadow-md p-4 mb-8">
            <ul class="flex flex-wrap gap-4 justify-center">
                <li><a href="/" class="text-blue-600 hover:text-blue-800 flex items-center"><i class="fa fa-home mr-1"></i>首页</a></li>
                <li><a href="/result" class="text-blue-600 hover:text-blue-800 flex items-center"><i class="fa fa-bar-chart mr-1"></i>预测结果</a></li>
                <li><a href="/suggestions" class="text-blue-600 hover:text-blue-800 flex items-center"><i class="fa fa-lightbulb-o mr-1"></i>营销建议</a></li>
                {% if current_user.is_admin %}
                <li><a href="/admin" class="text-purple-600 hover:text-purple-800 flex items-center"><i class="fa fa-cog mr-1"></i>系统管理</a></li>
                {% endif %}
                <li><a href="/logout" class="text-red-600 hover:text-red-800 flex items-center"><i class="fa fa-sign-out mr-1"></i>退出登录</a></li>
            </ul>
        </nav>

        {% if not result %}
        <div class="bg-yellow-50 border border-yellow-200 text-yellow-700 p-4 rounded-md mb-6 text-center">
            <p>请先在首页填写乘客信息并进行预测</p>
            <a href="/" class="text-blue-600 hover:underline mt-2 inline-block">返回首页</a>
        </div>
        {% else %}
        <!-- 建议概览 -->
        <div class="bg-white rounded-lg shadow-md p-6 mb-8">
            <div class="flex items-start">
                <div class="flex-shrink-0 mt-1">
                    <i class="fa fa-lightbulb-o text-2xl text-purple-600"></i>
                </div>
                <div class="ml-4">
                    <h2 class="text-xl font-semibold text-gray-800 mb-2">个性化营销建议</h2>
                    <p class="text-gray-600">
                        根据乘客信息和预测结果（付费概率: <span class="font-medium">{{ (result.probability * 100)|round(1) }}%</span>），
                        我们提供以下针对性建议：
                    </p>
                </div>
            </div>
        </div>

        <!-- 建议列表 -->
        <div class="bg-white rounded-lg shadow-md p-6 mb-8">
            <ul class="space-y-4">
                {% for suggestion in result.suggestions %}
                <li class="flex">
                    <div class="flex-shrink-0 h-6 w-6 rounded-full bg-purple-100 flex items-center justify-center mt-0.5">
                        <i class="fa fa-check text-purple-600 text-xs"></i>
                    </div>
                    <p class="ml-4 text-gray-700">{{ suggestion }}</p>
                </li>
                {% endfor %}
            </ul>
        </div>

        <!-- 操作按钮 -->
        <div class="flex justify-between mt-8">
            <a href="/result" class="bg-gray-600 text-white py-2 px-6 rounded-md hover:bg-gray-700 transition flex items-center">
                <i class="fa fa-arrow-left mr-2"></i>返回结果页
            </a>
            <a href="/" class="bg-blue-600 text-white py-2 px-6 rounded-md hover:bg-blue-700 transition flex items-center">
                开始新的预测 <i class="fa fa-refresh ml-2"></i>
            </a>
        </div>
        {% endif %}

        <footer class="mt-12 text-center text-gray-500 text-sm">
            <p>航班乘客付费选座预测系统 &copy; 2023</p>
        </footer>
    </div>
</body>
</html>''',

        # 新增：管理页面模板
        'admin.html': '''<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=">
    <title>系统管理 - 航班乘客付费选座预测系统</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body class="bg-gray-100">
    <div class="container mx-auto px-4 py-8 max-w-6xl">
        <header class="text-center mb-8">
            <h1 class="text-3xl font-bold text-gray-800">航班乘客付费选座预测系统</h1>
            <p class="text-gray-600 mt-2">系统管理面板</p>
        </header>

        <!-- 导航菜单 -->
        <nav class="bg-white rounded-lg shadow-md p-4 mb-8">
            <ul class="flex flex-wrap gap-4 justify-center">
                <li><a href="/" class="text-blue-600 hover:text-blue-800 flex items-center"><i class="fa fa-home mr-1"></i>首页</a></li>
                <li><a href="/admin" class="text-purple-600 hover:text-purple-800 flex items-center border-b-2 border-purple-600"><i class="fa fa-cog mr-1"></i>管理面板</a></li>
                <li><a href="/logout" class="text-red-600 hover:text-red-800 flex items-center"><i class="fa fa-sign-out mr-1"></i>退出登录</a></li>
                <li class="ml-auto text-gray-600">管理员: {{ current_user.username }}</li>
            </ul>
        </nav>

        <!-- 信息卡片区 -->
        <div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
            <div class="bg-gradient-to-r from-blue-500 to-blue-700 rounded-lg shadow-md text-white p-6">
                <div class="flex justify-between items-center">
                    <div>
                        <p class="text-sm uppercase">总用户数</p>
                        <p class="text-3xl font-bold mt-1">{{ stats.total_users }}</p>
                    </div>
                    <i class="fa fa-users text-3xl opacity-80"></i>
                </div>
                <div class="mt-4">
                    <p class="text-sm">今日新用户: {{ stats.new_users_today }}</p>
                </div>
            </div>

            <div class="bg-gradient-to-r from-green-500 to-green-700 rounded-lg shadow-md text-white p-6">
                <div class="flex justify-between items-center">
                    <div>
                        <p class="text-sm uppercase">预测记录数</p>
                        <p class="text-3xl font-bold mt-1">{{ stats.total_predictions }}</p>
                    </div>
                    <i class="fa fa-bar-chart text-3xl opacity-80"></i>
                </极div>
                <div class="mt-4">
                    <p class="text-sm">今日预测次数: {{ stats.predictions_today }}</p>
                </div>
            </div>

            <div class="bg-gradient-to-r from-purple-500 to-purple-700 rounded-lg shadow-md text-white p-6">
                <div class="flex justify-between items-center">
                    <div>
                        <p class="text-sm uppercase">当前模型</p>
                        <p class="text-xl font-bold mt-1">{{ model_info.name }} v{{ model_info.version }}</p>
                    </div>
                    <i class="fa fa-cogs text-3xl opacity-80"></i>
                </div>
                <div class="mt-4">
                    <p class="text-sm">数据记录: {{ model_info.data_size }} | 准确率: {{ model_info.accuracy|round(2)*100 }}%</p>
                </div>
            </div>
        </div>

        <!-- 性能图表 -->
        <div class="bg-white rounded-lg shadow-md p-6 mb-8">
            <h2 class="text-xl font-semibold text-gray-800 mb-4">模型性能历史 <span class="text-gray-500 text-sm font-normal">(近30天)</span></h2>
            <div class="h-64">
                {{ performance_chart|safe }}
            </div>
        </div>

        <!-- 模型管理 -->
        <div class="bg-white rounded-lg shadow-md p-6 mb-8">
            <h2 class="text-xl font-semibold text-gray-800 mb-4">模型管理</h2>
            <div class="flex flex-wrap gap-4">
                <form action="/admin/train_model" method="post">
                    <button type="submit" class="bg-green-600 hover:bg-green-700 text-white px-6 py-3 rounded-md flex items-center">
                        <i class="fa fa-refresh mr-2"></i>立即重新训练模型
                    </button>
                </form>

                <form action="/admin/integrate_data">
                    <button type="submit" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-3 rounded-md flex items-center">
                        <i class="fa fa-database mr-2"></i>整合新数据
                    </button>
                </form>
            </div>

            <div class="mt-6">
                <h3 class="text-lg font-medium text-gray-700 mb-2">模型训练历史</h3>
                <div class="bg-gray-50 p-4 rounded-lg max-h-64 overflow-y-auto">
                    <table class="w-full text-sm text-left">
                        <thead>
                            <tr class="border-b">
                                <th class="py-2">时间</th>
                                <th class="py-2">模型</th>
                                <th class="py-2">准确率</th>
                                <th class="py-2">训练样本</th>
                            </tr>
                        </thead>
                        <tbody>
                            {% for log in training_logs %}
                            <tr class="border-b hover:bg-gray-100">
                                <td class="py-3">{{ log.created_at.strftime('%Y-%m-%d %H:%M') }}</td>
                                <td class="py-3">{{ log.model_name }} v{{ log.version }}</td>
                                <td class="py-3">{{ (log.accuracy * 100)|round(2) }}%</td>
                                <td class="py-3">{{ log.training_size }}</td>
                            </tr>
                            {% endfor %}
                        </tbody>
                    </table>
                </div>
            </div>
        </div>

        <!-- 用户行为分析 -->
        <div class="bg-white rounded-lg shadow-md p-6">
            <h2 class="text-xl font-semibold text-gray-800 mb-4">用户活跃度分析</h2>
            <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
                <div class="bg-gray-50 p-4 rounded-lg">
                    <h3 class="text-lg font-medium text-gray-700 mb-3">活跃用户排行</h3>
                    <div class="space-y-3">
                        {% for user in active_users %}
                        <div class="flex items-center justify-between">
                            <div class="flex items-center">
                                <div class="w-8 h-8 rounded-full bg-blue-500 text-white flex items-center justify-center">
                                    <span class="text-xs">{{ user.username[0].upper() }}</span>
                                </div>
                                <div class="ml-3">
                                    <p class="text-gray-800 font-medium">{{ user.username }}{% if user.is_admin %} <span class="text-xs bg-purple-100 text-purple-800 px-2 py-1 rounded-full">管理员</span>{% endif %}</p>
                                    <p class="text-xs text-gray-500">注册于 {{ user.created_at.strftime('%Y-%m-%d') }}</p>
                                </div>
                            </div>
                            <div class="text-right">
                                <p class="font-bold">{{ user.prediction_count }}</p>
                                <p class="text-xs text-gray-500">预测次数</p>
                            </div>
                        </div>
                        {% endfor %}
                    </div>
                </div>

                <div class="bg-gray-50 p-4 rounded-lg">
                    <h3 class="text-lg font-medium text-gray-700 mb-3">用户行为统计</h3>
                    <div class="grid grid-cols-2 gap-4">
                        <div class="bg-white p-4 rounded-lg shadow-sm border">
                            <p class="text-3xl font-bold text-center">{{ stats.avg_prediction_count|round(1) }}</p>
                            <p class="text-sm text-center text-gray-600">人均预测次数</p>
                        </div>
                        <div class="bg-white p-4 rounded-lg shadow-sm border">
                            <p class="text-3xl font-bold text-center">{{ stats.active_users }}</p>
                            <p class="text-sm text-center text-gray-600">活跃用户(近7天)</p>
                        </div>
                        <div class="bg-white p-4 rounded-lg shadow-sm border">
                            <p class="text-3xl font-bold text-center">{{ stats.admin_users }}</p>
                            <p class="text-sm text-center text-gray-600">管理员用户</p>
                        </div>
                        <div class="bg-white p4 rounded-lg shadow-sm border">
                            <p class="text-3xl font-bold text-center">{{ stats.new_users_7days }}</p>
                            <p class="text-sm text-center text-gray-600">7日内新用户</p>
                        </div>
                    </div>
                </div>
            </div>
        </div>

        <footer class="mt-12 text-center text-gray-500 text-sm">
            <p>航班乘客付费选座预测系统 &copy; 2023 | 管理员面板</p>
        </footer>
    </div>
</body>
</html>'''
    }

    # 创建所有模板文件
    for filename, content in templates.items():
        file_path = os.path.join('templates', filename)
        with open(file_path, 'w', encoding='utf-8') as f:
            f.write(content)
            logger.info(f"更新模板文件: {file_path}")


# 数据处理和模型训练函数
def archive_old_data():
    """归档旧数据"""
    if os.path.exists(DATA_PATH):
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        archive_file = f"{ARCHIVE_PATH}flight_passengers_{timestamp}.csv"
        shutil.copy2(DATA_PATH, archive_file)
        logger.info(f"已归档旧数据至: {archive_file}")


def integrate_new_data():
    """整合新数据到主数据集"""
    if not os.path.exists(NEW_DATA_PATH):
        logger.info("无新数据需要整合")
        return False, 0

    try:
        existing_data = pd.read_csv(DATA_PATH) if os.path.exists(DATA_PATH) else pd.DataFrame()
        new_data = pd.read_csv(NEW_DATA_PATH)

        # 数据格式验证
        required_columns = ['age', 'gender', 'travel_purpose', 'booking_window',
                            'flight_duration', 'cabin_class', 'frequent_flyer',
                            'baggage', 'meal_preference', 'flight_time', 'paid_seat']

        if not set(required_columns).issubset(new_data.columns):
            missing = set(required_columns) - set(new_data.columns)
            logger.error(f"新数据缺少必要列: {missing}")
            return False, 0

        new_data_count = len(new_data)

        # 处理重复数据
        combined_data = pd.concat([existing_data, new_data], ignore_index=True)
        combined_data = combined_data.drop_duplicates()

        # 保存整合后的数据
        archive_old_data()
        combined_data.to_csv(DATA_PATH, index=False)
        logger.info(f"已整合新数据，新增 {new_data_count} 条记录，总记录数: {len(combined_data)}")

        # 清空新数据文件
        os.remove(NEW_DATA_PATH)
        return True, new_data_count

    except Exception as e:
        logger.error(f"整合新数据失败: {str(e)}")
        return False, 0


def generate_sample_data(force_regenerate=False):
    """生成示例数据，确保所有变量提前定义"""
    if os.path.exists(DATA_PATH) and not force_regenerate:
        logger.info("示例数据已存在，使用现有数据")
        return pd.read_csv(DATA_PATH)

    np.random.seed(42)
    n_samples = 15000  # 样本数量

    # 1. 生成所有需要的变量（关键：在创建DataFrame前定义）
    # 年龄：18-80岁之间的随机数
    age = np.random.normal(40, 15, n_samples).astype(int)
    age = np.clip(age, 18, 80)  # 限制范围

    # 性别：男女比例约55:44，其他1%
    gender = np.random.choice(['Male', 'Female', 'Other'], n_samples, p=[0.55, 0.44, 0.01])

    # 旅行目的：商务、休闲、家庭
    travel_purpose = np.random.choice(
        ['Business', 'Leisure', 'Family'],
        n_samples,
        p=[0.25, 0.6, 0.15]
    )

    # 预订提前天数：1-180天
    booking_window = np.random.lognormal(3, 1, n_samples).astype(int)
    booking_window = np.clip(booking_window, 1, 180)

    # 飞行时长：1-12小时
    flight_duration = np.random.choice(
        [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
        n_samples,
        p=[0.2, 0.2, 0.15, 0.1, 0.1, 0.08, 0.05, 0.04, 0.03, 0.02, 0.015, 0.015]
    )

    # 客舱等级：经济舱占比最高
    cabin_class = np.random.choice(
        ['Economy', 'Premium Economy', 'Business', 'First'],
        n_samples,
        p=[0.65, 0.2, 0.1, 0.05]
    )

    # 其他特征
    frequent_flyer = np.random.choice(['Yes', 'No'], n_samples, p=[0.15, 0.85])
    baggage = np.random.choice(['None', '1 piece', '2+ pieces'], n_samples, p=[0.2, 0.6, 0.2])
    meal_preference = np.random.choice(['Yes', 'No'], n_samples, p=[0.7, 0.3])
    flight_time = np.random.choice(
        ['Morning', 'Afternoon', 'Evening', 'Night'],
        n_samples,
        p=[0.3, 0.3, 0.3, 0.1]
    )

    # 目标变量：是否付费选座
    paid_seat = np.random.choice([0, 1], n_samples, p=[0.7, 0.3])  # 基础概率30%

    # 2. 创建DataFrame（此时所有变量均已定义）
    data = pd.DataFrame({
        'age': age,
        'gender': gender,
        'travel_purpose': travel_purpose,
        'booking_window': booking_window,
        'flight_duration': flight_duration,
        'cabin_class': cabin_class,
        'frequent_flyer': frequent_flyer,
        'baggage': baggage,
        'meal_preference': meal_preference,
        'flight_time': flight_time,
        'is_long_flight': flight_duration >= 6,  # 长途飞行（>=6小时）
        'is_early_booking': booking_window >= 30,  # 提前预订（>=30天）
        'paid_seat': paid_seat
    })

    # 保存数据
    data.to_csv(DATA_PATH, index=False)
    logger.info(f"生成示例数据: {n_samples} 条记录")
    return data


def train_model(force_retrain=False):
    """训练优化的模型，确保所有特征正确处理"""
    if not force_retrain and all(os.path.exists(path) for path in [
        MODEL_PATH, SCALER_PATH, ENCODERS_PATH, FEATURE_COLUMNS_PATH
    ]):
        logger.info("使用现有模型，未进行强制重训练")
        return load_model()

    start_time = datetime.now()
    # 强制生成新数据（确保包含所有特征）
    data = generate_sample_data(force_regenerate=True)
    data = data.dropna()
    logger.info(f"训练数据量: {len(data)} 条记录")

    # 明确包含所有需要的特征，包括交互特征
    feature_columns = [
        'age', 'gender', 'travel_purpose', 'booking_window',
        'flight_duration', 'cabin_class', 'frequent_flyer',
        'baggage', 'meal_preference', 'flight_time',
        'is_long_flight',  # 明确包含
        'is_early_booking'  # 明确包含
    ]
    logger.info(f"训练使用的特征列: {feature_columns}")

    # 分离特征和目标变量
    X = data[feature_columns]
    y = data['paid_seat']  # 目标变量

    # 划分训练集和测试集
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

    # 关键修改：显式定义分类特征列表，确保包含两个布尔特征
    categorical_features = [
        'gender', 'travel_purpose', 'cabin_class',
        'frequent_flyer', 'baggage', 'meal_preference',
        'flight_time', 'is_long_flight', 'is_early_booking'  # 强制包含新特征
    ]
    numerical_features = [col for col in feature_columns if col not in categorical_features]
    logger.info(f"分类特征: {categorical_features}")
    logger.info(f"数值特征: {numerical_features}")

    # 编码分类特征并保存编码器
    encoders = {}
    for column in categorical_features:
        # 确保特征存在于训练数据中
        if column not in X_train.columns:
            logger.error(f"训练数据中缺少必要特征: {column}")
            raise ValueError(f"训练数据中缺少必要特征: {column}")

        le = LabelEncoder()
        # 处理布尔值特征的特殊情况（确保正确转换）
        if X_train[column].dtype == 'bool':
            X_train.loc[:, column] = X_train[column].astype(int)
            X_test.loc[:, column] = X_test[column].astype(int)

        X_train.loc[:, column] = le.fit_transform(X_train[column])
        X_test.loc[:, column] = le.transform(X_test[column])
        encoders[column] = le
        logger.info(f"已编码特征: {column}，类别: {le.classes_}")

    # 标准化数值特征
    scaler = StandardScaler()
    X_train[numerical_features] = scaler.fit_transform(X_train[numerical_features])
    X_test[numerical_features] = scaler.transform(X_test[numerical_features])
    logger.info(f"已标准化数值特征: {numerical_features}")

    # 训练模型（随机森林）
    model = RandomForestClassifier(
        n_estimators=100,
        max_depth=10,
        random_state=42,
        class_weight='balanced'
    )
    model.fit(X_train, y_train)

    # 评估模型
    y_pred = model.predict(X_test)
    accuracy = accuracy_score(y_test, y_pred)
    precision = precision_score(y_test, y_pred)
    recall = recall_score(y_test, y_pred)
    f1 = f1_score(y_test, y_pred)

    logger.info(f"模型评估: 准确率={accuracy:.4f}, 精确率={precision:.4f}, 召回率={recall:.4f}, F1={f1:.4f}")

    # 保存模型组件
    joblib.dump(model, MODEL_PATH)
    joblib.dump(scaler, SCALER_PATH)
    joblib.dump(encoders, ENCODERS_PATH)
    joblib.dump(feature_columns, FEATURE_COLUMNS_PATH)
    logger.info(f"特征列已保存: {FEATURE_COLUMNS_PATH}")

    # 记录训练日志
    training_time = (datetime.now() - start_time).total_seconds()
    version = datetime.now().strftime("%Y%m%d%H%M")
    with open(MODEL_VERSION_PATH, 'w') as f:
        f.write(f"{version}|{accuracy:.4f}|RandomForestClassifier")

    # 保存训练记录到数据库
    training_log = ModelTrainingLog(
        model_name="RandomForestClassifier",
        version=version,
        accuracy=accuracy,
        precision=precision,
        recall=recall,
        f1=f1,
        training_size=len(X_train),
        training_time=training_time
    )
    db.session.add(training_log)
    db.session.commit()

    logger.info(f"模型训练完成，耗时 {training_time:.2f} 秒")
    return model, scaler, encoders, feature_columns, data


def load_model():
    """加载模型，处理版本兼容性"""
    try:
        # 检查模型文件完整性
        required_files = [MODEL_PATH, SCALER_PATH, ENCODERS_PATH, FEATURE_COLUMNS_PATH]
        if not all(os.path.exists(path) for path in required_files):
            logger.info("模型文件不完整，重新训练...")
            return train_model()

        # 加载模型组件
        model = joblib.load(MODEL_PATH)
        scaler = joblib.load(SCALER_PATH)
        encoders = joblib.load(ENCODERS_PATH)
        feature_columns = joblib.load(FEATURE_COLUMNS_PATH)
        data = pd.read_csv(DATA_PATH) if os.path.exists(DATA_PATH) else generate_sample_data()

        logger.info("模型加载成功")
        feature_columns = joblib.load(FEATURE_COLUMNS_PATH)  # 加载最新的特征列
        return model, scaler, encoders, feature_columns, data

    except Exception as e:
        logger.error(f"加载失败: {str(e)}，重新训练...")
        return train_model()


def auto_retrain_model():
    """自动重新训练模型（每周执行）"""
    # 获取上次训练时间
    last_train_time = datetime.min
    training_logs = ModelTrainingLog.query.order_by(ModelTrainingLog.created_at.desc()).first()
    if training_logs:
        last_train_time = training_logs.created_at

    # 如果超过7天未训练，则重新训练
    if datetime.now() - last_train_time > timedelta(days=7):
        logger.info("触发每周自动模型重训练")
        train_model(force_retrain=True)
    else:
        logger.info("距离上次训练时间不足7天，跳过自动训练")


def save_prediction_to_db(input_data, prediction, probability):
    """将预测记录保存到数据库"""
    try:
        user = User.query.get(session['user_id'])
        record = PredictionRecord(
            user_id=user.id,
            age=input_data['age'],
            gender=input_data['gender'],
            travel_purpose=input_data['travel_purpose'],
            booking_window=input_data['booking_window'],
            flight_duration=input_data['flight_duration'],
            cabin_class=input_data['cabin_class'],
            is_long_flight=input_data['is_long_flight'],
            is_early_booking=input_data['is_early_booking'],
            prediction=prediction,
            probability=probability
        )

        # 更新用户预测次数
        user.prediction_count = user.prediction_count + 1
        db.session.add(record)
        db.session.commit()
        return True
    except Exception as e:
        logger.error(f"保存预测记录失败: {str(e)}")
        return False


def save_to_new_data_file(input_data):
    """将用户输入数据保存到新数据文件（用于后续模型更新）"""
    try:
        # 添加默认值
        input_data['frequent_flyer'] = 'No'
        input_data['baggage'] = '1 piece'
        input_data['meal_preference'] = 'Yes'
        input_data['flight_time'] = 'Afternoon'
        input_data['paid_seat'] = -1  # 标记为未知（模型预测后没有实际结果）

        # 检查文件是否存在，如果不存在则创建并写入表头
        file_exists = os.path.isfile(NEW_DATA_PATH)

        with open(NEW_DATA_PATH, 'a', newline='', encoding='utf-8') as f:
            writer = csv.DictWriter(f, fieldnames=input_data.keys())
            if not file_exists:
                writer.writeheader()
            writer.writerow(input_data)

        return True
    except Exception as e:
        logger.error(f"保存到新数据文件失败: {str(e)}")
        return False


# 用户认证路由
@app.route('/login', methods=['GET', 'POST'])
def login():
    """用户登录"""
    # 如果已登录，直接跳转到首页
    if 'user_id' in session:
        return redirect(url_for('index'))

    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']

        # 验证用户名长度
        if not (1 <= len(username) <= 8):
            flash('用户名长度必须为1-8个字符', 'danger')
            return render_template('login.html')

        user = User.query.filter_by(username=username).first()

        if user and user.check_password(password):
            session['user_id'] = user.id

            # 更新最后登录时间和登录次数
            user.last_login = datetime.utcnow()
            db.session.commit()

            next_page = request.args.get('next', url_for('index'))
            flash('登录成功！', 'success')
            return redirect(next_page)
        else:
            flash('用户名或密码不正确', 'danger')

    return render_template('login.html')


@app.route('/register', methods=['GET', 'POST'])
def register():
    """用户注册"""
    # 如果已登录，直接跳转到首页
    if 'user_id' in session:
        return redirect(url_for('index'))

    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        confirm_password = request.form['confirm_password']

        # 验证用户名长度
        if not (1 <= len(username) <= 8):
            flash('用户名长度必须为1-8个字符', 'danger')
            return render_template('register.html')

        # 验证密码是否一致
        if password != confirm_password:
            flash('两次输入的密码不一致', 'danger')
            return render_template('register.html')

        # 验证用户名是否已存在
        if User.query.filter_by(username=username).first():
            flash('用户名已存在', 'danger')
            return render_template('register.html')

        # 创建新用户
        user = User(username=username)
        # 第一个用户设为管理员
        if User.query.count() == 0:
            user.is_admin = True
        user.set_password(password)

        try:
            db.session.add(user)
            db.session.commit()
            flash('注册成功，请登录', 'success')
            return redirect(url_for('login'))
        except Exception as e:
            db.session.rollback()
            flash(f'注册失败: {str(e)}', 'danger')

    return render_template('register.html')


@app.route('/logout')
def logout():
    """用户退出登录"""
    session.pop('user_id', None)
    flash('已成功退出登录', 'info')
    return redirect(url_for('login'))


# 业务功能路由
@app.route('/')
@login_required
def index():
    """首页：乘客信息填写表单"""
    current_user = User.query.get(session['user_id'])
    return render_template('index.html', current_user=current_user)


@app.route('/predict', methods=['POST'])
@login_required
def predict():
    """处理预测请求，确保特征处理与训练时一致"""
    # 动态加载模型和特征列（强制重新加载，确保最新）
    model, scaler, encoders, feature_columns, _ = load_model()
    logging.debug(f"动态加载的特征列: {feature_columns}")

    try:
        # 1. 获取表单数据并构建基础输入
        input_data = {
            'age': int(request.form['age']),
            'gender': request.form['gender'],
            'travel_purpose': request.form['travel_purpose'],
            'booking_window': int(request.form['booking_window']),
            'flight_duration': int(request.form['flight_duration']),
            'cabin_class': request.form['cabin_class'],
            'is_long_flight': int(request.form['flight_duration']) >= 6,  # 计算布尔特征
            'is_early_booking': int(request.form['booking_window']) >= 30  # 计算布尔特征
        }
        logging.debug(f'原始表单数据: {input_data}')

        # 2. 输入验证
        errors = []
        if not (18 <= input_data['age'] <= 80):
            errors.append('年龄必须在18-80岁之间')
        if not (1 <= input_data['booking_window'] <= 180):
            errors.append('预订提前天数必须在1-180天之间')
        if not (1 <= input_data['flight_duration'] <= 24):
            errors.append('飞行时长必须在1-24小时之间')

        if errors:
            logging.debug(f'输入验证失败: {errors}')
            current_user = User.query.get(session['user_id'])
            return render_template('index.html', errors=errors, form_data=input_data, current_user=current_user)

        # 3. 补充必要的默认值（与训练数据结构匹配）
        input_data['frequent_flyer'] = 'No'
        input_data['baggage'] = '1 piece'
        input_data['meal_preference'] = 'Yes'
        input_data['flight_time'] = 'Afternoon'
        logging.debug(f'补充默认值后的数据: {input_data}')

        # 4. 构建输入DataFrame（严格匹配训练时的特征列）
        df = pd.DataFrame([input_data], columns=feature_columns)
        logging.debug(f'构建的输入DataFrame（特征顺序与训练一致）: \n{df}')

        # 5. 显式定义特征类型（与训练时完全一致）
        categorical_features = [
            'gender', 'travel_purpose', 'cabin_class',
            'frequent_flyer', 'baggage', 'meal_preference',
            'flight_time', 'is_long_flight', 'is_early_booking'
        ]
        numerical_features = [col for col in feature_columns if col not in categorical_features]
        logging.debug(f"预测时分类特征: {categorical_features}")
        logging.debug(f"预测时数值特征: {numerical_features}")

        # 6. 编码分类特征（仅处理预定义的分类特征）
        for feature in categorical_features:
            if feature not in encoders:
                logging.error(f"编码器缺失特征: {feature}，请重新训练模型")
                raise ValueError(f"模型缺少必要的特征编码器: {feature}")

            # 处理布尔值特征（转为整数）
            if df[feature].dtype == 'bool':
                df.loc[:, feature] = df[feature].astype(int)

            try:
                df.loc[:, feature] = encoders[feature].transform(df[feature])
            except ValueError:
                # 处理未见过的类别（使用最常见类别）
                most_common = np.argmax(encoders[feature].transform(encoders[feature].classes_))
                df.loc[:, feature] = most_common
                logging.warning(f"特征 {feature} 出现未训练的类别，使用最常见值: {most_common}")
        logging.debug(f'分类特征编码后的数据: \n{df}')

        # 7. 标准化数值特征（仅处理预定义的数值特征）
        try:
            df[numerical_features] = scaler.transform(df[numerical_features])
        except Exception as e:
            logging.error(f"数值特征标准化失败: {str(e)}")
            raise ValueError(f"特征标准化错误: {str(e)}")
        logging.debug(f'数值特征标准化后的数据: \n{df}')

        # 8. 执行预测
        prediction = model.predict(df)[0]
        probability = model.predict_proba(df)[0][1]
        logging.debug(f'模型预测结果: 付费意愿={prediction}，概率={probability:.4f}')

        # 9. 生成营销建议
        suggestions = generate_suggestions(input_data, probability)
        logging.debug(f'生成的营销建议: {suggestions}')

        # 10. 保存结果到会话和数据库
        session['prediction_result'] = {
            'input_data': input_data,
            'prediction': int(prediction),
            'probability': float(probability),
            'suggestions': suggestions
        }
        save_prediction_to_db(input_data, prediction, probability)
        save_to_new_data_file(input_data)

        return redirect(url_for('result'))

    except Exception as e:
        logging.error(f"预测过程出错: {str(e)}", exc_info=True)
        current_user = User.query.get(session['user_id'])
        return render_template('index.html',
                               errors=[f'预测失败: {str(e)}，请重试'],
                               current_user=current_user)


@app.route('/result')
@login_required
def result():
    """预测结果页面"""
    # 从会话中获取预测结果
    result_data = session.get('prediction_result', None)
    current_user = User.query.get(session['user_id'])
    return render_template('result.html', result=result_data, current_user=current_user)


@app.route('/suggestions')
@login_required
def suggestions():
    """建议页面"""
    # 从会话中获取预测结果
    result_data = session.get('prediction_result', None)
    current_user = User.query.get(session['user_id'])
    return render_template('suggestions.html', result=result_data, current_user=current_user)


# 新增：管理页面路由
@app.route('/admin')
@login_required
@admin_required
def admin_dashboard():
    """管理员仪表盘"""
    current_user = User.query.get(session['user_id'])

    # 获取统计数据
    stats = {
        'total_users': User.query.count(),
        'new_users_today': User.query.filter(
            func.date(User.created_at) == datetime.today().date()
        ).count(),
        'total_predictions': PredictionRecord.query.count(),
        'predictions_today': PredictionRecord.query.filter(
            func.date(PredictionRecord.created_at) == datetime.today().date()
        ).count(),
        'active_users': User.query.join(PredictionRecord).filter(
            PredictionRecord.created_at >= datetime.now() - timedelta(days=7)
        ).distinct().count(),
        'admin_users': User.query.filter_by(is_admin=True).count(),
        'new_users_7days': User.query.filter(
            User.created_at >= datetime.now() - timedelta(days=7)
        ).count(),
        'avg_prediction_count': db.session.query(
            func.avg(User.prediction_count)
        ).scalar() or 0
    }

    # 获取模型信息
    model_info = {
        'name': '未知',
        'version': '0.0',
        'data_size': 0,
        'accuracy': 0
    }
    latest_model_log = ModelTrainingLog.query.order_by(ModelTrainingLog.created_at.desc()).first()
    if latest_model_log:
        model_info = {
            'name': latest_model_log.model_name,
            'version': latest_model_log.version,
            'data_size': latest_model_log.training_size,
            'accuracy': latest_model_log.accuracy
        }
    elif os.path.exists(MODEL_VERSION_PATH):
        with open(MODEL_VERSION_PATH, 'r') as f:
            version_info = f.read().split('|')
            if len(version_info) >= 3:
                model_info['name'] = version_info[2]
                model_info['version'] = version_info[0]

    # 获取训练日志
    training_logs = ModelTrainingLog.query.order_by(ModelTrainingLog.created_at.desc()).limit(5).all()

    # 获取活跃用户
    active_users = User.query.order_by(User.prediction_count.desc()).limit(5).all()

    # 生成性能图表
    performance_chart = generate_performance_chart()

    return render_template('admin.html',
                           current_user=current_user,
                           stats=stats,
                           model_info=model_info,
                           training_logs=training_logs,
                           active_users=active_users,
                           performance_chart=performance_chart)


@app.route('/admin/train_model', methods=['POST'])
@login_required
@admin_required
def train_model_admin():
    """管理员触发模型训练"""
    try:
        train_model(force_retrain=True)
        flash('模型训练成功！', 'success')
    except Exception as e:
        logger.error(f"模型训练失败: {str(e)}")
        flash(f'模型训练失败: {str(e)}', 'danger')
    return redirect(url_for('admin_dashboard'))


@app.route('/admin/integrate_data')
@login_required
@admin_required
def integrate_data_admin():
    """管理员触发数据整合"""
    success, count = integrate_new_data()
    if success and count > 0:
        flash(f'成功整合 {count} 条新数据', 'success')
    elif success:
        flash('没有新数据需要整合', 'info')
    else:
        flash('数据整合失败', 'danger')
    return redirect(url_for('admin_dashboard'))


# 辅助函数：生成营销建议
def generate_suggestions(input_data, probability):
    """根据乘客信息和预测概率生成营销建议"""
    suggestions = []

    # 高概率情况（>=70%）
    if probability >= 0.7:
        suggestions.append("该乘客付费意愿强烈，可推荐高级选座服务（如紧急出口、前排座位）")
        if input_data['is_long_flight']:
            suggestions.append("结合长途飞行特点，可捆绑推荐舒适套餐（额外腿部空间+餐食）")
        if input_data['travel_purpose'] == 'Business':
            suggestions.append("商务旅客可推荐优先登机+付费选座组合优惠")

    # 中等概率（30%-70%）
    elif 0.3 <= probability < 0.7:
        suggestions.append("该乘客有一定付费意愿，可提供限时折扣（如选座8折优惠）")
        if input_data['booking_window'] >= 30:
            suggestions.append("提前预订用户，可推荐'早鸟选座优惠'")
        suggestions.append("推荐热门座位（如靠窗位）时强调稀缺性，促进决策")

    # 低概率（<30%）
    else:
        suggestions.append("该乘客付费意愿较低，可提供小额优惠（如10元选座券）吸引尝试")
        if input_data['cabin_class'] == 'Economy':
            suggestions.append("经济舱用户可推荐性价比高的基础选座服务")
        suggestions.append("结合旅行目的推荐相关附加服务（如家庭旅客推荐相邻座位）")

    # 通用建议
    if input_data['travel_purpose'] == 'Family':
        suggestions.append("家庭旅客可推荐'家庭座位包'，一次性选择多个相邻座位")
    if input_data['flight_duration'] >= 6:
        suggestions.append("长途飞行用户可强调舒适座位对旅行体验的重要性")

    return suggestions


# 辅助函数：生成性能图表
def generate_performance_chart():
    """生成模型性能历史图表"""
    # 获取近30天的模型性能数据
    thirty_days_ago = datetime.now() - timedelta(days=30)
    performance_data = ModelTrainingLog.query.filter(
        ModelTrainingLog.created_at >= thirty_days_ago
    ).order_by(ModelTrainingLog.created_at).all()

    if not performance_data:
        return "<p>暂无性能数据</p>"

    # 准备图表数据
    dates = [log.created_at.strftime('%Y-%m-%d') for log in performance_data]
    accuracies = [log.accuracy * 100 for log in performance_data]
    precisions = [log.precision * 100 for log in performance_data]
    recalls = [log.recall * 100 for log in performance_data]

    # 创建图表
    data = [
        go.Scatter(x=dates, y=accuracies, mode='lines+markers', name='准确率'),
        go.Scatter(x=dates, y=precisions, mode='lines+markers', name='精确率'),
        go.Scatter(x=dates, y=recalls, mode='lines+markers', name='召回率')
    ]

    layout = go.Layout(
        title='模型性能趋势',
        xaxis={'title': '日期'},
        yaxis={'title': '百分比(%)', 'range': [0, 100]},
        legend={'orientation': 'h', 'y': -0.2}
    )

    fig = go.Figure(data=data, layout=layout)
    return fig.to_html(full_html=False)


# 应用初始化
with app.app_context():
    # 创建数据库表
    db.create_all()
    # 确保模板文件存在
    create_template_files()
    # 加载或训练模型
    model, scaler, encoders, feature_columns, _ = load_model()
    # 检查是否需要自动重新训练模型
    auto_retrain_model()

# 启动应用
if __name__ == '__main__':
    # 确保所有目录存在
    for dir_path in ['models', 'data', 'templates', ARCHIVE_PATH, MODEL_HISTORY_PATH]:
        if not os.path.exists(dir_path):
            os.makedirs(dir_path)

    # 关键：强制重建数据库表
    with app.app_context():
        db.drop_all()  # 删除旧表（开发环境使用，会清空数据）
        db.create_all()  # 根据最新模型创建新表
        print("数据库表已重新创建，包含 last_login 字段")

    # 启动服务
    app.run(debug=True)